package net.sansi.v3correctserver.service.camera;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.sansi.v3correctserver.config.CacheConfiguration;
import net.sansi.v3correctserver.constenum.CacheKeys;
import net.sansi.v3correctserver.constenum.CameraConst;
import net.sansi.v3correctserver.exception.ServiceException;
import net.sansi.v3correctserver.service.camera.cameras.Canon7DExeCamera;
import net.sansi.v3correctserver.service.camera.cameras.ServerCamera;
import net.sansi.v3correctserver.service.camera.cameras.TestCamera;
import net.sansi.v3correctserver.util.CmmUtil;
import net.sansi.v3correctserver.util.SearchUtil;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Fang
 * create time : 2024/11/12
 * IDE : IntelliJ IDEA
 * <p>
 * 相机
 */
public abstract class Camera {

    /**
     * 获取相机名称
     *
     * @return 相机名称（每个相机实现必须保持名称唯一）
     */
    abstract public String getName();

    /**
     * 获取相机拍摄相片的后缀
     *
     * @return 相机拍摄相片的后缀
     */
    abstract public String getSuffix();

    /**
     * 拍摄照片
     *
     * @param params 拍摄照片
     * @return 是否拍摄成功
     */
    public boolean takePhoto(Camera.Params params, Path savePath) {
        CameraTcpService service = SpringUtil.getBean(CameraTcpService.class);
        String url = StrUtil.format("/take/{}/{}/{}/{}/Dir:{}",
                getName(),
                params.getTv(),
                params.getAv(),
                params.getIso(),
                savePath.toAbsolutePath()
        );
        String resp = service.request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("拍照失败 [request] {} [error] {}", url, resp);
    }

    ;

    // 缓存相机参数表
    private static final Map<String, Options> optionsCache = new ConcurrentHashMap<>();

    /**
     * 获取相机参数可选项
     *
     * @param forceRequest 强制请求，如果为true，则强制请求相机参数，否则优先使用缓存信息
     * @return 相机参数可选项
     */
    public Options readOptions(boolean forceRequest) {
        String cameraName = getName();
        if (!forceRequest && optionsCache.containsKey(cameraName)) return optionsCache.get(cameraName);
        String url = StrUtil.format("/query/params/{}", cameraName);
        String resp = CameraTcpService.getInstance().request(url);
        String[] split = resp.split("\\|");
        Camera.Options options = new Camera.Options();
        for (String sec : split) {
            String[] sp = sec.split(":");
            String[] values = sp[1].trim().split(",");
            List<Integer> vs = new ArrayList<>();
            for (String v : values) {
                vs.add(Integer.parseInt(v));
            }
            vs.sort(Comparator.comparingInt(a -> a));
            if (sp[0].equalsIgnoreCase("tv")) options.setTvs(vs);
            if (sp[0].equalsIgnoreCase("iso")) options.setIsos(vs);
            if (sp[0].equalsIgnoreCase("av")) options.setAvs(vs);
        }
        optionsCache.put(cameraName, options);
        return options;
    }

    /**
     * 读取相机模式
     *
     * @return 相机模式
     */
    public String readMode() {
        String url = StrUtil.format("/query/mode/{}",
                getName()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if (CameraConst.MODE_TAKE.equalsIgnoreCase(resp)) return CameraConst.MODE_TAKE;
        if (CameraConst.MODE_PREVIEW.equalsIgnoreCase(resp)) return CameraConst.MODE_PREVIEW;
        throw new ServiceException("读取模式错误 [request] {} [error] {}", url, resp);
    }

    /**
     * 写入相机参数
     *
     * @param params 参数
     * @return 是否写入成功
     */
    public boolean writeParams(Params params) {
        String url = StrUtil.format("/set/params/{}/{}/{}/{}",
                getName(),
                params.getTv(),
                params.getAv(),
                params.getIso()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("设置相机参数失败 [request] {} [error] {}", url, resp);
    }

    /**
     * 检查相机是否可以正常使用
     *
     * @return 是否可以正常使用
     */
    public boolean checkReady() {
        String url = StrUtil.format("/check/ready/{}",
                getName()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("ready".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("检查相机有效性失败 [request] {} [error] {}", url, resp);
    }

    /**
     * 开始预览
     *
     * @return 是否成功
     */
    public boolean startPreview(Path savePath) {
        String url = StrUtil.format("/preview/start/{}/Dir:{}",
                getName(),
                savePath.toAbsolutePath()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("开始预览失败 [request] {} [error] {}", url, resp);
    }

    /**
     * 停止预览
     *
     * @return 是否成功
     */
    public boolean stopPreview() {
        String url = StrUtil.format("/preview/stop/{}",
                getName()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("停止预览失败 [request] {} [error] {}", url, resp);
    }

    /**
     * 拍摄预览
     *
     * @return 是否成功
     */
    public boolean takePreview(Path savePath) {
        String url = StrUtil.format("/preview/take/{}/Dir:{}",
                getName(),
                savePath.toAbsolutePath()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("预览图拍摄失败 [request] {} [error] {}", url, resp);
    }

    /**
     * 断开相机连接
     *
     * @return 是否成功
     */
    public boolean disconnect() {
        String url = StrUtil.format("/disconnect/{}",
                getName()
        );
        String resp = CameraTcpService.getInstance().request(url);
        if ("success".equalsIgnoreCase(resp)) return true;
        throw new ServiceException("断开相机连接失败 [request] {} [error] {}", url, resp);
    }


    /**
     * 相机列表
     *
     * @return 相机列表
     */
    public static List<Camera> cameras() {
        return CmmUtil.cacheGetCallback(CacheConfiguration.FOREVER, CacheKeys.Forever.CAMERAS, () -> {
            List<Camera> res = new ArrayList<>(List.of(
                    new TestCamera(),
                    new Canon7DExeCamera()
            ));
            try {
                String url = StrUtil.format("/query/cameras");
                String resp = CameraTcpService.getInstance().request(url);
                if (StrUtil.isBlank(resp)) return res;
                String[] split = resp.split(",");
                for (String s : split) {
                    String[] nameSuffix = s.split(":");
                    String name = nameSuffix[0], suffix = nameSuffix[1];
                    res.add(new ServerCamera(name, suffix));
                }
                return res;
            } catch (Exception e) {
                return res;
            }
        });
    }

    /**
     * 通过相机名称找到相机
     *
     * @param name 相机名称
     * @return 相机
     */
    public static Camera find(String name) {
        if (name == null) return null;
        return cameras().stream()
                .filter(camera -> camera.getName().equals(name))
                .findFirst()
                .orElse(null);
    }

    /**
     * 相机参数基类
     */
    @ApiModel("相机参数基类")
    @Data
    @Accessors(chain = true)
    public static class Params {
        public Params() {}

        public Params(Integer colorIdx) {
            this.colorIdx = colorIdx;
        }

        @ApiModelProperty("拍摄颜色索引：0 红色 1 绿色 2 蓝色")
        private Integer colorIdx;

        @ApiModelProperty("拍摄打屏亮度 0~255")
        private Integer brightness = 255;

        @ApiModelProperty(value = "iso", required = true)
        private Integer iso = 100;

        @ApiModelProperty(value = "光圈", required = true)
        private Integer av = 270;

        @ApiModelProperty(value = "快门", required = true)
        private Integer tv = 500;

        /**
         * 根据相机的参数列表
         * 将 iso, av, tv 设定为参数列表中最接近的值
         *
         * @param options 可选项
         */
        public void resetByOptions(Options options) {
            iso = SearchUtil.binarySearchClosestValue(options.isos, iso);
            av = SearchUtil.binarySearchClosestValue(options.avs, av);
            tv = SearchUtil.binarySearchClosestValue(options.tvs, tv);
        }
    }

    /**
     * 相机参数可选项
     */
    @ApiModel("相机参数可选项")
    @Data
    @Accessors(chain = true)
    public static class Options {

        @ApiModelProperty("快门列表")
        List<Integer> tvs;

        @ApiModelProperty("iso列表")
        List<Integer> isos;

        @ApiModelProperty("光圈列表")
        List<Integer> avs;

    }


    /**
     * 相机参数相类
     * tvs
     * avs
     * iso
     */
    @Getter
    @Setter
    public static class ParamItem {
        private String name;
        private Integer code;
        private Integer ms;
    }

    /**
     * 1"3
     * 1/180
     */
    public static class CanonTvItem extends ParamItem {
        public Integer getMs() {
            if (getName().contains("/")) {
                String[] sp = getName().split("/");
                return Integer.parseInt(sp[0]) * 1000 / Integer.parseInt(sp[1]);
            }
            if (getName().contains("\"")) {
                String[] sp = getName().split("\"");
                int s = Integer.parseInt(sp[0]) * 1000;
                if (sp.length > 1) {
                    s = s + Integer.parseInt(sp[1]) * 100;
                }
                return s;
            }
            return -1;
        }
    }


}
